﻿using CTP.CRM.Application.Dtos.OAuth;
using CTP.CRM.Application.Dtos.User.Input;
using CTP.CRM.Application.Dtos.User.Output;
using CTP.CRM.Core;
using CTP.CRM.Core.Common.Constants;
using CTP.CRM.Core.Common.Dto;
using CTP.CRM.Core.Common.Enums;
using CTP.CRM.Core.Helper;
using CTP.CRM.Core.Provider;
using CTP.CRM.Core.PublicMethords;
using CTP.CRM.Core.SharedModel.Enums;
using CTP.CRM.Core.SharedModel.Pub;
using CTP.CRM.Repository.Employee.Services;
using CTP.CRM.Repository.Employee.ValueObjects;
using CTP.CRM.Repository.User.Entitys;
using CTP.CRM.Repository.User.Services;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Configuration;
using Volo.Abp;
using Volo.Abp.Application.Services;
using Volo.Abp.DependencyInjection;
using Volo.Abp.EventBus.Local;
using Volo.Abp.ObjectMapping;
using Volo.Abp.Validation;

namespace CTP.CRM.Application.Services.OAuth
{
    /// <summary>
    /// 身份认证服务
    /// </summary>
    [ApiExplorerSettings(GroupName = "身份认证")]
    public class OAuthService : IApplicationService, IValidationEnabled, ITransientDependency
    {
        private const string LoginInvalid = "登录信息已失效，请重新登录.";
        private const string FsDataCacheKey = "UserCenter:FsData:";
        private const int Thirtydaysseconds = 30 * 24 * 60 * 60;
        private const string TooManyVisits = @"访问人数过多，请稍后再试.";

        private static readonly string Tenant = CTPABPCore.Configuration["CurrentTenant"];


        #region 构造函数

        private readonly IUserRepository _userRepository;
        private readonly ILocalEventBus _localEventBus;
        private readonly IAsymmetricEncryptionProvider _asymmetricEncryption;
        private readonly IHttpClientFactory _httpClientFactory;
        protected readonly IEmployeeRepository _employee;
        private readonly IObjectMapper _objectMapper;
        private readonly IConfiguration _configuration;
        private readonly DataLockedService _dataLockedService;
        public OAuthService(IUserRepository userRepository
            , ILocalEventBus localEventBus
            , IAsymmetricEncryptionProvider asymmetricEncryption
            , IHttpClientFactory httpClientFactory
             , IEmployeeRepository employee
            , IObjectMapper objectMapper,
    IConfiguration configuration, DataLockedService dataLockedService)
        {
            _userRepository = userRepository;
            _localEventBus = localEventBus;
            _asymmetricEncryption = asymmetricEncryption;
            _httpClientFactory = httpClientFactory;
            _employee = employee;
            _objectMapper = objectMapper;
            _configuration = configuration;
            _dataLockedService = dataLockedService;
        }

        #endregion 构造函数


        /// <summary>
        /// 模拟前端密码加密
        /// </summary>
        /// <param name="signInput"></param>
        /// <returns></returns>
        [AllowAnonymous, HttpPost("OAuth/password")]
        public BasicResponseDto Test([FromBody] string password)
        {
            return _asymmetricEncryption.Encryption(password);
        }

        /// <summary>
        /// 用户登录
        /// </summary>
        /// <param name="signInput"></param>
        /// <returns></returns>
        [AllowAnonymous, HttpPost("OAuth/Login"), Consumes("application/x-www-form-urlencoded")]
        public async Task<LoginOutput> Login([FromForm] LoginInput signInput)
        {
            //step1.取到用户
            string account = signInput.Account.ToLower();
            var dataKey = $"InnerUser:{account}";
            if (_dataLockedService.TryGetLocked(dataKey, out var lockedInfo))
            {
                throw new BusinessException(message: $"账号已被锁定，解锁时间:{lockedInfo.LockEndTime.ToString("yyyy-MM-dd HH:mm")}!");
            }

            UserEntity user = await _userRepository.GetFirstAsync(x => x.Account.ToLower() == account || x.TelPhone == account);

            if (user == null)
            {
                _dataLockedService.AddLocked(dataKey, out var lockedMsg);
                ThrowHelper.ThrowMessage(lockedMsg);
            }

            if (!user.IsEnable)
            {
                _dataLockedService.AddLocked(dataKey, out var lockedMsg);
                ThrowHelper.ThrowMessage("账号被禁用，请联系管理员处理！ " + lockedMsg);
            }

            //step.2 非对称解密，md5+盐加密
            string targetPwd = DecodeRSAtoMD5(signInput.Password, user.Salt, dataKey);

            //step.3 校验数据库账密，组装数据
            if (user.PassWord != targetPwd)
            {
                _dataLockedService.AddLocked(dataKey, out var lockedMsg);
                ThrowHelper.ThrowMessage(lockedMsg);
            }

            //已经在其他设备上登录
            //var userIdKey = $"usertoken:userId:{user.Id}";
            //var isUserIdKey = await RedisHelper.Client.ExistsAsync(userIdKey);
            //if (isUserIdKey)
            //{
            //    ThrowHelper.ThrowIfNotCodition(isUserIdKey, "已经在其他设备上登录，是否强制旧用户下线!");
            //}

            //step.4 生成token，写入登入记录，返回
            string token = await GeneratorTokenAndWriteHeaderAsync(user);
            var userInfo = _objectMapper.Map<UserEntity, UserInfoOutput>(user);
            if (userInfo is UserInfoOutput it)
            {
                await _localEventBus.PublishAsync(new WriteLoginLogEvent(LoginMode.用户登录, it.Id, ""));
            }
            // 是否初始密码
            string targetInitPwd = SystemNames.默认密码.GetMD5(user.Salt);
            var result = new LoginOutput(userInfo, token);
            result.IsInitPwd = targetInitPwd == targetPwd;

            if (lockedInfo != null) _dataLockedService.Unlocked(dataKey);
            //step.5 登录写入token至Redis
            //await WriteLoginRedisToken(signInput, user, token);
            return result;
        }




        /// <summary>
        /// 根据用户生成Token并写入响应头
        /// </summary>
        /// <param name="user"></param>
        /// <returns></returns>
        private async Task<string> GeneratorTokenAndWriteHeaderAsync(UserEntity user)
        {
            if (user.EmployeeId.HasValue)
            {
                var emps = await _employee.GetEmpLoginValueObjectAsync(user.EmployeeId.Value);
                EmployeeLoginValueObject empValueObject = emps.Find(x => x.IsMasterPosition == true);
                if (empValueObject != null)
                {
                    ThrowHelper.ThrowIfCodition(empValueObject?.JobStatus == JobStatus.离职, "员工已离职!");
                    ThrowHelper.ThrowIfCodition(empValueObject.IsDeleted, "员工信息不存在!");
                }
            }

            GenerateTokenInput tokenInput = _objectMapper.Map<UserEntity, GenerateTokenInput>(user);
            tokenInput.AssignTokenOrigin(TokenOrigin.内部用户);
            string gToken = TokenGeneratorHelper.GeneratorToken(tokenInput.MapUserToClaims());
            string rToken = TokenGeneratorHelper.GeneratorRefreshToken(tokenInput.MapUserToClaims());
            WriteResponeHeader(gToken, rToken);
            //if (Tenant == BasicNames.汇佰)
            //{
            //    await _oAuthCacheOperService.CacheTokenAsync(gToken, tokenInput.Id.ToString());
            //}
            return gToken;
        }


        /// <summary>
        /// 解密用户密码，并Md5
        /// </summary>
        /// <param name="passWord"></param>
        /// <param name="salt"></param>
        /// <returns></returns>
        private string DecodeRSAtoMD5(string passWord, string salt, string? dataKey = null)
        {
            BasicResponseDto basicResponse = _asymmetricEncryption.DeEncryption(passWord);
            if (dataKey != null && !basicResponse.Success)
            {
                _dataLockedService.AddLocked(dataKey, out string lockedMsg);
                throw new BusinessException(message: lockedMsg);
            }

            ThrowHelper.ThrowIfNotCodition(basicResponse.Success, "密码错误!");
            string deEncryptionPassword = basicResponse.Data?.ToString() ?? string.Empty;
            return deEncryptionPassword.GetMD5(salt);
        }

        private void WriteResponeHeader(string token, string refreshToken)
        {
            string expiredTime = CTPABPCore.Configuration["JWTSettings:ExpiredTime"] ?? "0";
            string clockSkew = CTPABPCore.Configuration["JWTSettings:ClockSkew"] ?? "0";
            int buffer = Convert.ToInt32(expiredTime) - Convert.ToInt32(clockSkew);

            var httpContext = CTPABPCore.HttpContext;
            httpContext.Response.Headers["Access-Control-Expose-Headers"] = "Authorization,X-Authorization,timespan,expired";
            httpContext.Response.Headers["Authorization"] = token;
            httpContext.Response.Headers["X-Authorization"] = refreshToken;
            httpContext.Response.Headers["timespan"] = UserCenterHelper.GetTimeSpan();
            httpContext.Response.Headers["expired"] = buffer.ToString();
        }
    }
}
